<template>
  <div ref="containerRef" style="width: 100%; height: 100%"></div>
  <iframe
    src="./model/index-plush.html"
    frameborder="0"
    @load="onLoad"
    ref="iframeRef"
    v-if="open"
    style="opacity: 0"
  ></iframe>
</template>
<script setup>
import { ref, nextTick, onUnmounted, onMounted } from 'vue'
import { message as Message } from 'ant-design-vue'
import * as THREE from 'three/src/Three.js'
import * as Geometries from 'three/src/geometries/Geometries.js'
import { LineMaterial } from 'three/addons/lines/LineMaterial.js'
import { LineSegments2 } from 'three/addons/lines/LineSegments2.js'
import { LineSegmentsGeometry } from 'three/addons/lines/LineSegmentsGeometry.js'

import { BufferGeometryLoader } from 'three/src/loaders/BufferGeometryLoader.js'
import { OrbitControls } from '@/assets/js/OrbitControls.js'
import { GLTFLoader } from '@/assets/js/GLTFLoader.js'
import ResizeObserver from 'resize-observer-polyfill'

let props = defineProps({
  path: {
    required: true,
  },
  filename: {},
})

let emit = defineEmits(['complete'])

let open = ref(true)
let iframeRef = ref()
let containerRef = ref()
async function onMessage(event) {
  try {
    let url = event?.data?.gltf
    let error = event?.data?.error
    if (error) {
      throw error
    }
    if (!url) return
    await load(url)
    onResize()
    open.value = false
    emit('complete')
  } catch (e) {
    console.log(e)
    emit('complete', e)
    Message.error('渲染失败!')
  }
}
function onLoad() {
  window.addEventListener('message', onMessage)
  iframeRef.value.contentWindow.postMessage({
    url: props.path,
    filename: props.filename,
  })
}

onUnmounted(() => {
  window.removeEventListener('message', onMessage)
})

function createPlane(color = 0xffff00, size, position) {
  let geometry = new THREE.PlaneGeometry(...size)
  let material = new THREE.MeshBasicMaterial({
    color,
    side: THREE.DoubleSide,
    transparent: true,
    opacity: 0.3,
  })
  let plane = new THREE.Mesh(geometry, material)
  plane.position.set(...position)
  return plane
}

let camera, scene, renderer

let clipPlanes = [
  new THREE.Plane(new THREE.Vector3(-1, 0, 0), 0),
  new THREE.Plane(new THREE.Vector3(0, -1, 0), 0),
  new THREE.Plane(new THREE.Vector3(0, 0, -1), 0),
]
let clipInversionPlanes = [
  new THREE.Plane(new THREE.Vector3(1, 0, 0), 0),
  new THREE.Plane(new THREE.Vector3(0, 1, 0), 0),
  new THREE.Plane(new THREE.Vector3(0, 0, 1), 0),
]

let planeHelpers = []

let model

let size = new THREE.Vector3()
let center = new THREE.Vector3()
let box = new THREE.Box3()

function fitCameraToSelection(fitOffset = 2.2) {
  box.makeEmpty()
  for (let object of [group.children[0]]) {
    box.expandByObject(object)
  }

  box.getSize(size)
  box.getCenter(center)
  let maxSize = Math.max(size.x, size.y, size.z)
  let fitHeightDistance =
    maxSize / (2 * Math.atan((Math.PI * camera.fov) / 360))
  let fitWidthDistance = fitHeightDistance / camera.aspect
  let distance = fitOffset * Math.max(fitHeightDistance, fitWidthDistance)

  let direction = controls.target
    .clone()
    .sub(camera.position)
    .normalize()
    .multiplyScalar(distance)

  controls.maxDistance = distance * 10
  controls.target.copy(center)

  camera.near = distance / 100
  camera.far = distance * 100
  camera.updateProjectionMatrix()

  camera.position.copy(controls.target).sub(direction)

  controls.update()
}

function getBoundingBox() {
  return box
}

function lookFront() {
  let distance = camera.position.distanceTo(new THREE.Vector3())
  controls.target.set(0, 0, 0)
  camera.position.set(0, 0, distance)
  controls.update()
}
function lookBack() {
  let distance = camera.position.distanceTo(new THREE.Vector3())
  controls.target.set(0, 0, 0)
  camera.position.set(0, 0, -distance)
  controls.update()
}
function lookLeft() {
  let distance = camera.position.distanceTo(new THREE.Vector3())
  controls.target.set(0, 0, 0)
  camera.position.set(-distance, 0, 0)
  controls.update()
}
function lookRight() {
  let distance = camera.position.distanceTo(new THREE.Vector3())
  controls.target.set(0, 0, 0)
  camera.position.set(distance, 0, 0)
  controls.update()
}
function lookTop() {
  let distance = camera.position.distanceTo(new THREE.Vector3())
  controls.target.set(0, 0, 0)
  camera.position.set(0, distance, 0)
  controls.update()
}
function lookBottom() {
  let distance = camera.position.distanceTo(new THREE.Vector3())
  controls.target.set(0, 0, 0)
  camera.position.set(0, -distance, 0)
  controls.update()
}
function lookIso() {
  let distance = camera.position.distanceTo(new THREE.Vector3())
  controls.target.set(0, 0, 0)
  camera.position.set(-distance / 2, distance / 2, distance / 2)
  controls.update()
}

function zoom(cameraSpeed = 0.1) {
  let targetPosition = new THREE.Vector3()
  let cameraPosition = camera.position.clone()
  let direction = targetPosition.sub(cameraPosition)
  camera.position.add(direction.multiplyScalar(cameraSpeed))
  camera.lookAt(targetPosition)
  controls.update()
}

function meshVisible(visible) {
  group.traverse(child => {
    if (!child.isLineSegments2 && child.isMesh) {
      child.visible = visible
    }
  })

  // group.children.find(({ myline, mypoint }) => !myline && !mypoint).visible =
  //   visible
  render()
}
let initLine = false
function lineVisible(visible) {
  // if (!initLine) {
  //   let line = model.clone()
  //   line.myline = true
  //   line.visible = false
  //   line.traverse(child => {
  //     if (child.isMesh) {
  //       // 创建 EdgesGeometry 以提取边缘
  //       let edges = new THREE.EdgesGeometry(child.geometry)
  //       // 创建线条材质
  //       let lineMaterial = new THREE.LineBasicMaterial({
  //         color: 0x083f00,
  //         linewidth: 2,
  //       })
  //       lineMaterial.clippingPlanes = clippingPlanesRecord
  //       lineMaterial.needsUpdate = true // 更新材质
  //       // 创建 LineSegments 来渲染边缘
  //       let line = new THREE.LineSegments(edges, lineMaterial)
  //       // line.visible = false
  //       // 将线条添加到 group 中
  //       child.parent.add(line)
  //       child.parent.remove(child)
  //     }
  //   })
  //   group.add(line)
  //   initLine = true
  // }

  // group.children.find(({ myline }) => myline).visible = visible

  group.traverse(child => {
    if (child.isLineSegments2) {
      child.visible = visible
    }
  })

  render()
}

let initPoint = false
function pointVisible(visible) {
  if (!initPoint) {
    let s = Math.max(size.x, size.y, size.z) / 100
    let point = model.clone()
    point.mypoint = true
    point.visible = false
    point.traverse(child => {
      if (child.isMesh) {
        // point.visible = false
        let geometry = child.geometry

        // 创建点材质，设置点的颜色和大小
        let pointsMaterial = new THREE.PointsMaterial({
          color: 0x9a0000, // 设置点的颜色为红色
          size: s, // 设置点的大小
        })
        pointsMaterial.clippingPlanes = clippingPlanesRecord
        pointsMaterial.needsUpdate = true // 更新材质
        // 创建 Points 对象，渲染顶点为点云
        let points = new THREE.Points(geometry, pointsMaterial)
        child.parent.add(points)
        child.parent.remove(child)
      }
    })
    group.add(point)
    initPoint = true
  }

  group.children.find(({ mypoint }) => mypoint).visible = visible
  render()
}

function createGridHelper(size, center) {
  let gridHelper = new THREE.GridHelper(Math.max(size.x, size.z) * 10, 100)
  gridHelper.position.y = center.y - size.y
  gridHelper.material.opacity = 0.25
  gridHelper.material.transparent = true
  scene.add(gridHelper)
}

function createAxesHelper(size) {
  let axesHelper = new THREE.AxesHelper(Math.max(size.x, size.y, size.z) * 0.6) //The X axis is red. The Y axis is green. The Z axis is blue.
  scene.add(axesHelper)
}

let clippingPlanesRecord = []
function setClip(
  plan = 2,
  visible = true,
  visibleHelper = true,
  constant = 0,
  inversion = false,
) {
  let clipFinalPlanes = inversion ? clipInversionPlanes : clipPlanes
  let clipPlane = clipFinalPlanes[plan]
  clipPlane.constant = inversion ? -constant : constant

  let planeHelper = planeHelpers[plan]
  planeHelper.visible = visibleHelper && visible
  if (plan === 0) {
    planeHelper.position.setX(constant)
  }
  if (plan === 1) {
    planeHelper.position.setY(constant)
  }
  if (plan === 2) {
    planeHelper.position.setZ(constant)
  }

  group.traverse(child => {
    if (
      child.isMesh ||
      child.isLine ||
      child?.material?.clippingPlanes instanceof Array
    ) {
      child.material.clippingPlanes = child.material.clippingPlanes.filter(
        clippingPlane => {
          return (
            clippingPlane !== clipPlane &&
            clipFinalPlanes.includes(clippingPlane)
          )
        },
      )
      if (visible) {
        child.material.clippingPlanes = [
          ...child.material.clippingPlanes,
          clipPlane,
        ]
        clippingPlanesRecord = [...child.material.clippingPlanes]
      }
    }
  })

  render()
}

let group
let controls
function init(url) {
  let rect = containerRef.value.getBoundingClientRect()
  renderer = new THREE.WebGLRenderer({
    // antialias: true,
  })
  renderer.setPixelRatio(window.devicePixelRatio)
  renderer.setSize(rect.width, rect.height)
  renderer.localClippingEnabled = true
  containerRef.value.appendChild(renderer.domElement)

  scene = new THREE.Scene()
  scene.background = new THREE.Color(0xffffff)

  camera = new THREE.PerspectiveCamera(
    40,
    window.innerWidth / window.innerHeight,
    1,
    200,
  )

  camera.position.set(-1.5, 2.5, 3.0)

  controls = new OrbitControls(camera, renderer.domElement)
  controls.addEventListener('change', render) // use only if there is no animation loop
  controls.minDistance = 1
  controls.maxDistance = 10
  // controls.enablePan = false
  // controls.maxPolarAngle = Math.PI / 2 // 无限制，极角可以旋转整个球面
  // controls.minPolarAngle = -Math.PI * 2 // 从正下方开始
  // console.log(controls)
  let light = new THREE.HemisphereLight(0xffffff, 0x999999, 2)
  light.position.set(0, 0, 1)
  // let helper = new THREE.HemisphereLightHelper(light, 5)
  // scene.add(helper)
  scene.add(light)
  // let light1 = new THREE.HemisphereLight(0xffffff, 0x999999, 2)
  // light.position.set(0, 0, 50)
  // let helper1 = new THREE.HemisphereLightHelper(light, 5)
  // scene.add(helper1)
  // scene.add(light1)

  const originalParseMaterials = THREE.ObjectLoader.prototype.parseMaterials
  const originalParseObject = THREE.ObjectLoader.prototype.parseObject
  const originalParseGeometries = THREE.ObjectLoader.prototype.parseGeometries

  THREE.ObjectLoader.prototype.parseMaterials = function parseMaterials(
    json,
    textures,
  ) {
    let LineMaterialsJson = json.filter(({ type }) => type === 'LineMaterial')
    let otherMaterialsJson = json.filter(({ type }) => type !== 'LineMaterial')
    const cache = {} // MultiMaterial
    const materials = {}
    if (json !== undefined) {
      // const loader = new MaterialLoader()
      // loader.setTextures(textures)
      let lineMaterial = new LineMaterial({
        color: 0x333333,
        linewidth: 1.5, // in world units with size attenuation, pixels otherwise
        side: THREE.DoubleSide,
        // worldUnits: true,
        // vertexColors: true,
        // transparent: true,
        // opacity: 0.2,
        // depthTest: false,
        // visible: false,
      })

      for (let i = 0, l = LineMaterialsJson.length; i < l; i++) {
        const data = LineMaterialsJson[i]

        if (cache[data.uuid] === undefined) {
          cache[data.uuid] = lineMaterial
        }

        materials[data.uuid] = cache[data.uuid]
      }
    }

    let otherMaterials = originalParseMaterials.call(
      this,
      otherMaterialsJson,
      textures,
    )
    return { ...materials, ...otherMaterials }
  }

  THREE.ObjectLoader.prototype.parseGeometries = function parseGeometries(
    json,
    shapes,
  ) {
    let LineSegmentsGeometryJson = json.filter(
      ({ type }) => type === 'LineSegmentsGeometry',
    )
    let otherGeometryJson = json.filter(
      ({ type }) => type !== 'LineSegmentsGeometry',
    )
    // console.log(json, shapes)
    // debugger
    const geometries = {}

    if (json !== undefined) {
      for (let i = 0, l = LineSegmentsGeometryJson.length; i < l; i++) {
        const geometry = new LineSegmentsGeometry()
        const data = LineSegmentsGeometryJson[i]
        geometry.setPositions(data.$$$positions)
        // geometry.setColors(
        //   Array(data.data.attributes.position.array.length / 3),
        // )
        geometry.computeBoundingBox()

        geometry.uuid = data.uuid

        if (data.name !== undefined) geometry.name = data.name
        if (data.userData !== undefined) geometry.userData = data.userData

        geometries[data.uuid] = geometry
      }
    }

    let otherGeometrys = originalParseGeometries(otherGeometryJson, shapes)

    return { ...geometries, ...otherGeometrys }
  }

  THREE.ObjectLoader.prototype.parseObject = function parseObject(
    data,
    geometries,
    materials,
    textures,
    animations,
  ) {
    if (data.type === 'LineSegments2') {
      function getGeometry(name) {
        if (geometries[name] === undefined) {
          console.warn('THREE.ObjectLoader: Undefined geometry', name)
        }

        return geometries[name]
      }

      function getMaterial(name) {
        if (name === undefined) return undefined

        if (Array.isArray(name)) {
          const array = []

          for (let i = 0, l = name.length; i < l; i++) {
            const uuid = name[i]

            if (materials[uuid] === undefined) {
              console.warn('THREE.ObjectLoader: Undefined material', uuid)
            }

            array.push(materials[uuid])
          }

          return array
        }

        if (materials[name] === undefined) {
          console.warn('THREE.ObjectLoader: Undefined material', name)
        }

        return materials[name]
      }
      return new LineSegments2(
        getGeometry(data.geometry),
        getMaterial(data.material),
      )
    } else {
      return originalParseObject.call(
        this,
        data,
        geometries,
        materials,
        textures,
        animations,
      )
    }
  }

  group = new THREE.Group()
  // let loader = new GLTFLoader()

  return new Promise((resolve, reject) => {
    let loader = new THREE.ObjectLoader()
    loader.load(
      // resource URL
      url,

      // onLoad callback
      // Here the loaded data is assumed to be an object
      async function (object) {
        model = object
        group.add(model)
        // console.log(model)
        model.traverse(child => {
          if (child.isMesh) {
            if (!child.isLineSegments2) {
              child.material = new THREE.MeshPhongMaterial({
                color: 0xaaaaaa,
                side: THREE.DoubleSide,
              })
            }
            // child.material.side = 2
            child.material.clippingPlanes = []
            // child.material.clipShadows = true // 可选：启用阴影裁剪
            child.material.needsUpdate = true // 更新材质
          }
        })

        // group.traverse(child => {
        //   if (child.isMesh || child.isLine) {
        //     // child.material.side = 2
        //     child.material.clippingPlanes = []
        //     // child.material.clipShadows = true // 可选：启用阴影裁剪
        //     child.material.needsUpdate = true // 更新材质
        //   }
        // })
        scene.add(group)

        await renderer.compileAsync(model, camera, scene)

        fitCameraToSelection(2.2)

        createGridHelper(size, center)
        createAxesHelper(size)

        let planeHelper1 = createPlane(
          0xeeeeee,
          [size.z, size.y],
          [center.x, center.y, center.z],
        )
        planeHelper1.rotation.y = Math.PI / 2

        let planeHelper2 = createPlane(
          0xeeeeee,
          [size.z, size.x],
          [center.x, center.y, center.z],
        )
        planeHelper2.rotation.x = Math.PI / 2
        planeHelper2.rotation.z = Math.PI / 2

        let planeHelper3 = createPlane(
          0xeeeeee,
          [size.x, size.y],
          [center.x, center.y, center.z],
        )
        planeHelper1.visible = false
        planeHelper2.visible = false
        planeHelper3.visible = false
        scene.add(planeHelper1)
        scene.add(planeHelper2)
        scene.add(planeHelper3)

        planeHelpers = [planeHelper1, planeHelper2, planeHelper3]

        clipPlanes[0].constant = center.x
        clipPlanes[1].constant = center.y
        clipPlanes[2].constant = center.z
        render()
        resolve()
      },

      // onProgress callback
      function () {},

      reject,
    )

    // loader.load(
    //   url,
    //   async function (gltf) {
    //     model = gltf.scene
    //     // wait until the model can be added to the scene without blocking due to shader compilation

    //     group.add(model)
    //     model.traverse(child => {
    //       if (child.isMesh) {
    //         child.material = new THREE.MeshPhongMaterial({
    //           color: 0xaaaaaa,
    //           side: THREE.DoubleSide,
    //         })
    //         // child.material.side = 2
    //         child.material.clippingPlanes = []
    //         // child.material.clipShadows = true // 可选：启用阴影裁剪
    //         child.material.needsUpdate = true // 更新材质
    //       }
    //     })

    //     // group.traverse(child => {
    //     //   if (child.isMesh || child.isLine) {
    //     //     // child.material.side = 2
    //     //     child.material.clippingPlanes = []
    //     //     // child.material.clipShadows = true // 可选：启用阴影裁剪
    //     //     child.material.needsUpdate = true // 更新材质
    //     //   }
    //     // })
    //     scene.add(group)

    //     await renderer.compileAsync(model, camera, scene)

    //     fitCameraToSelection(2.2)

    //     createGridHelper(size, center)
    //     createAxesHelper(size)

    //     let planeHelper1 = createPlane(
    //       0xeeeeee,
    //       [size.z, size.y],
    //       [center.x, center.y, center.z],
    //     )
    //     planeHelper1.rotation.y = Math.PI / 2

    //     let planeHelper2 = createPlane(
    //       0xeeeeee,
    //       [size.z, size.x],
    //       [center.x, center.y, center.z],
    //     )
    //     planeHelper2.rotation.x = Math.PI / 2
    //     planeHelper2.rotation.z = Math.PI / 2

    //     let planeHelper3 = createPlane(
    //       0xeeeeee,
    //       [size.x, size.y],
    //       [center.x, center.y, center.z],
    //     )
    //     planeHelper1.visible = false
    //     planeHelper2.visible = false
    //     planeHelper3.visible = false
    //     scene.add(planeHelper1)
    //     scene.add(planeHelper2)
    //     scene.add(planeHelper3)

    //     planeHelpers = [planeHelper1, planeHelper2, planeHelper3]

    //     clipPlanes[0].constant = center.x
    //     clipPlanes[1].constant = center.y
    //     clipPlanes[2].constant = center.z
    //     render()
    //     resolve()
    //   },
    //   () => {},
    //   reject,
    // )
  })
}

function onResize() {
  let ro = new ResizeObserver((entries, observer) => {
    for (let entry of entries) {
      let { width, height } = entry.contentRect
      camera.aspect = window.innerWidth / window.innerHeight
      camera.updateProjectionMatrix()
      renderer.setSize(width, height)
      render()
    }
  })
  ro.observe(containerRef.value)
}

function render() {
  renderer.render(scene, camera)
}

async function load(url) {
  await nextTick()
  init(url)
}

defineExpose({
  zoom,
  lookFront,
  lookBack,
  lookLeft,
  lookRight,
  lookTop,
  lookBottom,
  lookIso,
  lineVisible,
  meshVisible,
  pointVisible,
  setClip,
  getBoundingBox,
  fitCameraToSelection,
})
</script>
